#!/usr/bin/env python
# -*- coding: utf-8 -*-

NAME=u'锄大地'
CONF=dict(
    player_cards=[[4, 1]], 
    cardBases=[3, 2, 4], 
)

HELP=u'''　　　　　　　　　　　　　　　锄　大　地

游戏简介：

　　锄大地是中国人自己发明的扑克牌游戏，具有规则简单易学，打法生动精彩的特点，并且颇具发展成为竞技性智力游戏的潜力。锄大地较之竞技项目桥牌，运气成分偏大一些，但这一点也使其群众基础更为广泛。锄大地是在相互配合中尽量求得最大的利益，且敌我关系转瞬即变，并不固定，这成为锄大地最大的魅力所在。

详细规则、胜负判定方法：

　　游戏的胜利者是第一个出完全部手牌的游戏者。游戏一共使用扑克牌52张(去掉大小王)，四人游戏，每家13张牌。

上手基本简单玩法：

牌型：

　　锄大地的出牌牌型有以下一些：

　　单张：任何一张单牌。

　　一对：二张牌点相同的牌。

　　三个：三张牌点相同的牌。

　　四个：四张牌点相同的牌。

　　顺：连续五张牌点相邻的牌，如“34567”“910JQK”“10JQKA”“A2345”等，顺的张数必须是5张，A既可在顺的最后，也可在顺的最前，但不能在顺的中间，如“JQKA2”不是顺。

　　杂顺：花色不全部相同的牌称为杂顺。

　　同花顺：每张牌的花色都相同的顺称为同花顺。

　　同花：由相同花色的五张牌组成，但不是顺，称“同花”。如红桃“278JK”。

　　三个带一对：例如：99955。

　　四个带单张：例如：99995。

牌的大小：

　　1.只有张数相同的牌可以比较大小，例如：99 > 88，J > 10，但不能比较：99 和 10。

　　2.单张牌的大小：首先比较牌点，如果牌点相同再比较牌的花色。牌点从大到小依次为：A K Q J 10 9 8 7 6 5 4 3 2 (规则1)、或 2 A K Q J 10 9 8 7 6 5 4 3 (规则2)、或 3 2 A K Q J 10 9 8 7 6 5 4 (规则3)。花色从大到小的顺序为：黑桃、红桃、梅花、方块。比如：黑桃9 > 红桃9 > 梅花9 > 方块9 > 黑桃8 。

　　3.其它牌型在进行比较时都取其中一张最大的牌按单张的方式进行比较：三个带一对时，取三个中的最大一张。四个带单张时，取四个中最大的一张进行比较。

　　4.顺子中最大的一张进行比较，注意A在顺子中作为小牌，如：5 4 3 2 A顺子比较时，只取5进行比较；A在和K相连作顺时，按大牌进行比较。

　　5.五张牌的牌型中，同花顺最大，四个带单张第二，三个带一对第三，同花五第四，杂顺最小。也就是说，上家出了杂顺后，用户的任何一副同花五、三个带一对、四个带单张或同花顺都比杂顺大。

　　6.在同花或同花顺中，规则1和规则3：先按花色进行比较，花色相同再按最大牌的点数比较；规则2：先按最大牌的点数进行比较，点数相同再按花色比较。

出牌规则：

　　1.第一局都由拿方块2(规则1)或方块3(规则2)或方块4(规则3)的一方首先出牌，而且第一轮出牌中必须包含方块2(规则1)或方块3(规则2)或方块4(规则3)。以后每局牌都由上局牌获胜者(第一个打完手中牌的一方)出牌，并且第一轮牌不需要包含方块2(规则1)或方块3(规则2)或方块4(规则3)。

　　2.首家可以出任何一种合法的牌型。

　　3.首家出牌后，下家所出的牌张数必须和首家的相同，同时比首家所出的牌大；下家也可以不出牌，由再下一家继续出牌。

　　4.如果连续三家都不出牌，这时最后出牌的一家可以重新打出新的牌型。

　　5.如此继续，直到四家有一方手中的牌全部打光为止。

　　6.当一家只剩下一张牌时，它的上家要出最大的单张牌或者多张牌。

积分说明：

　　在一局游戏结束后，手中剩余的牌张数为n，n<7(规则1和规则3)或n<8(规则2)时，牌分为n，7<=n<=9(规则1和规则3)或8<=n<=9(规则2)时，牌分为2n，10<=n<=13时，牌分为3n，n=13时，牌分为4n 。

'''

import general
from server import *
from client import *

#游戏开始必须出的牌，最小牌列表，按牌基数排列
baseCards=[Card(DIAMOND, THREE), Card(DIAMOND, TWO), Card(DIAMOND, FOUR)]

#牌型
CARDTYPE=[
    #非法，单张牌，一对牌，三张同数，四张同数
    ILLEGAL, SINGLE, PAIR, TRIAD, QUAD,
    #顺子，同花，三带二，四带一，同花顺
    STRAIGHT, FLUSH, TRIAD_PAIR, QUAD_SINGLE, FLUSH_STRAIGHT,
]=range(10)
def getCardType(cards):
    '''公共方法，获得出牌类型，包括出牌基数'''
    count=len(cards)
    if count==0:
        type=ILLEGAL
        base=None
        return type, base

    elif count==1:
        #单张
        type=SINGLE
        base=cards[0]
        return type, base

    elif count==2:
        c0=Card(cards[0])
        c1=Card(cards[1])
        if c0.face==c1.face:
            #一对
            type=PAIR
            if c0.suit>c1.suit:
                base=cards[0]
            else:
                base=cards[1]
            return type, base

    elif count==3:
        ocards=[Card(cid) for cid in cards]
        if ocards[0].face==ocards[1].face==ocards[2].face:
            #三张
            type=TRIAD
            base=cards[0]
            for cid in cards:
                if cid>base:
                    base=cid
            return type, base

    elif count==4:
        ocards=[Card(cid) for cid in cards]
        if ocards[0].face==ocards[1].face==ocards[2].face==ocards[3].face:
            #四张
            type=QUAD
            base=cards[0]
            for cid in cards:
                if cid>base:
                    base=cid
            return type, base

    elif count==5:
        ocards=[Card(cid) for cid in cards]
        
        flush=False
        if ocards[0].suit==ocards[1].suit==ocards[2].suit==ocards[3].suit==ocards[4].suit:
            #同花
            flush=True
        
        straight=False
        #按面值排序
        ocards.sort(key=lambda x:x.face)
        if ocards[0].face==ACE and ocards[-1].face==KING:
            #ACE作大的情况，调整排序
            ocards.append(ocards.pop(0))
        #求差值
        diffs=[(ocards[i+1].face-ocards[i].face)%13 for i in range(4)]
        if diffs[0]==diffs[1]==diffs[2]==diffs[3]==1:
            #顺子
            straight=True
        
        if flush and straight:
            type=FLUSH_STRAIGHT
            base=ocards[-1].id
            return type, base
        elif flush:
            type=FLUSH
            base=ocards[-1].id
            return type, base
        elif straight:
            type=STRAIGHT
            base=ocards[-1].id
            return type, base
        else:
            #看是否有三带二或四带一
            sameface=0
            for d in diffs:
                if d==0:
                    sameface+=1
            if sameface==3:
                #差值列表恰好3个0才有可能
                if diffs[0]==diffs[3]==0:
                    #差值头尾均为0的是三带二，否则是四带一，取中间牌为base
                    type=TRIAD_PAIR
                else:
                    type=QUAD_SINGLE
                base=ocards[2].id
                return type, base

    #否则不合法
    type=ILLEGAL
    base=None
    return type, base

UNKNOWN, LESS, EQUAL, BIGGER=range(4)
def compareCards(x, y, order):
    '''公共方法，比较两组牌大小'''
    lenx, leny=len(x), len(y)
    if lenx!=leny:
        #牌数不对肯定不可比
        return UNKNOWN
    
    #获取各自的牌型
    cardtypex, basex=getCardType(x)
    if cardtypex==ILLEGAL:
        #牌型不合法
        return UNKNOWN
    cardtypey, basey=getCardType(y)
    if cardtypey==ILLEGAL:
        #牌型不合法
        return UNKNOWN
    #牌型均合法
    if cardtypex!=cardtypey:
        #牌型不同
        if lenx==leny==5:
            #比较5张的情况
            if cardtypex>cardtypey:
                return BIGGER
            else:
                return LESS
        #非法情况
        return UNKNOWN
    
    #牌型相同但要比较花色的情况
    if cardtypex==cardtypey and cardtypex in [FLUSH, FLUSH_STRAIGHT]:
        #同花或同花顺时先比较花色
        suitx=Card(x[0]).suit
        suity=Card(y[0]).suit
        if suitx>suity:
            return BIGGER
        elif suitx<suity:
            return LESS
    
    #牌型相同比较基数
    if order[basex]>order[basey]:
        return BIGGER
    elif order[basex]==order[basey]:
        return EQUAL
    else:
        return LESS

def calcCards(selfcards, mcards, order):
    '''计算出牌'''
    selfcards.sort(key=lambda x:order[x])
    selfocards=[Card(cid) for cid in selfcards]

    #牌按面值归类
    facelist=[[] for i in FACE]
    for c in selfocards:
        facelist[c.face].append(c)

    #牌按重牌数归类
    countlist=[[] for i in SUIT]
    for f in FACE:
        data=facelist[f]
        countlist[len(data)].append(data)
    
    #牌按花色归类
    suitlist=[[] for i in SUIT]
    for c in selfocards:
        suitlist[c.suit].append(c)
    
    #记录可出的牌
    result=[]
    
    mcardtype, mbase=getCardType(mcards)
    if mcardtype in [SINGLE, PAIR, TRIAD, QUAD]:
        index=mcardtype-SINGLE+1
        for ocards in countlist[index]:
            cid=ocards[-1].id
            if order[cid]>order[mbase]:
                cards=[oc.id for oc in ocards]
                return cards
        #TODO:没有合法的，不拆牌
        return []
    else:
        straights=[]
        if ILLEGAL<mcardtype<=STRAIGHT:
            #找顺子
            unifaces=[f for f in FACE if facelist[f]]
            unifaces.sort()
            l=len(unifaces)
            if l>=5:
                for i in range(0, l-4):
                    u0, u1, u2, u3, u4=unifaces[i:i+5]
                    if u1-u0==u2-u1==u3-u2==u4-u3==1:
                        #TODO:只取每个面值的最大的牌来组成顺子
                        straight=[facelist[u][-1].id for u in unifaces[i:i+5]]
                        straights.append(straight)
                result+=straights
                for r in straights[::-1]:
                    #从结果中挑较大的比较
                    if compareCards(r, mcards, order)==BIGGER:
                        return r
        #继续
        flushs=[]
        if ILLEGAL<mcardtype<=FLUSH:
            #找同花
            for s in SUIT:
                l=len(suitlist[s])
                if l>=5:
                    #TODO:只取同花色最大的5张牌来组成
                    flush=[c.id for c in suitlist[s][-5:]]
                    flushs.append(flush)
            result+=flushs
            for r in flushs[::-1]:
                #从结果中挑较大的比较
                if compareCards(r, mcards, order)==BIGGER:
                    return r
        #继续
        triadpairs=[]
        if ILLEGAL<mcardtype<=TRIAD_PAIR:
            #找三带二
            for i in countlist[3]:
                for j in countlist[2]:
                    triadpair=[c.id for c in i+j]
                    triadpairs.append(triadpair)
                    break
            result+=triadpairs
            for r in triadpairs[::-1]:
                #从结果中挑较大的比较
                if compareCards(r, mcards, order)==BIGGER:
                    return r
        #继续
        quadsingles=[]
        if ILLEGAL<mcardtype<=QUAD_SINGLE:
            #找四带一
            for i in countlist[4]:
                for j in countlist[1]:
                    quadsingle=[c.id for c in i+j]
                    quadsingles.append(quadsingle)
                    break
            result+=quadsingles
            for r in quadsingles[::-1]:
                #从结果中挑较大的比较
                if compareCards(r, mcards, order)==BIGGER:
                    return r
        #继续
        flushstraights=[]
        if ILLEGAL<mcardtype<=FLUSH_STRAIGHT:
            #找同花顺，在同花色的牌中找顺子
            for s in SUIT:
                ocards=suitlist[s]
                faces=[oc.face for oc in ocards]
                l=len(ocards)
                if l>=5:
                    for i in range(0, l-4):
                        u0, u1, u2, u3, u4=faces[i:i+5]
                        if u1-u0==u2-u1==u3-u2==u4-u3==1:
                            #取顺子得同花顺
                            flushstraight=[oc.id for oc in ocards[i:i+5]]
                            flushstraights.append(flushstraight)
            result+=flushstraights
            for r in flushstraights[::-1]:
                #从结果中挑较大的比较
                if compareCards(r, mcards, order)==BIGGER:
                    return r
        
        #非法代表可以任意出
        if ILLEGAL==mcardtype:
            for cl in countlist:
                for ocards in cl:
                    cards=[oc.id for oc in ocards]
                    if cards:
                        result.append(cards)
            return random.choice(result)
        
        #无牌可出
        return []


class Game(general.Game):
    '''游戏类，锄大地'''
    def __init__(self):
        super(Game, self).__init__()
        
        #游戏模块名称
        self.moduleName='ChuDaDi'
        #游戏名称
        self.name=NAME
    
    def setCardOrder(self, base=3):
        '''设置排序'''
        order=[i*10 for i in range(13)]
        for i in range(base-1):
            c=order.pop()
            order.insert(0, c)
        order1=[i+1 for i in order]
        order2=[i+2 for i in order]
        order3=[i+3 for i in order]
        order=order+order1+order2+order3
        self.cardOrder=order
    
    def configure(self):
        '''追加设置'''
        super(Game, self).configure()
        #设置基数牌
        bcIndex=CONF['cardBases'].index(self.cardBase)
        self.baseCard=baseCards[bcIndex]
        #设置牌序
        self.setCardOrder(self.cardBase)
        #设置牌堆
        self.table.setPileCards(range(52))
        #设置积分盒
        self.table.fillScoreBox([0 for i in range(self.playerCount)])

    def calcScore(self):
        '''计算玩家积分'''
        playerCardCount=[len(player.cardList) for player in self.players]
        for i in range(len(playerCardCount)):
            n=playerCardCount[i]
            if n<8:
                score=n
            elif 8<=n<10:
                score=2*n
            elif 10<=n<13:
                score=3*n
            else:
                score=4*n
            self.scoreBox[i]+=score

    def play(self):
        '''打牌'''
        #设置首轮最先出牌者为含有基数牌的玩家
        if self.round==1:
            for player in self.players:
                if self.baseCard.id in player.cardList:
                    self.setMarkPlayerId(player)
                    break
        super(Game, self).play()

    def judge(self, player, data):
        '''出牌有效判定'''
        #先排序
        data.sort(key=lambda x:self.cardOrder[x])
        if self.markPlayerId==player.info['id']:
            #上次被标记的是自己，表示这轮没人跟牌，允许任意出（不允许空），有最小牌要先出
            if not data:
                #不出牌
                return UNDO
            if self.round==1 and (self.baseCard.id not in data) and (self.baseCard.id in player.cardList):
                #有最小牌不出
                return UNDO
            cardtype, base=getCardType(data)
            if cardtype==ILLEGAL:
                #牌型不合法
                return UNDO
            return OK
        
        if not data:
            #不出牌，允许
            return OK

        #取得上次被标记玩家出的牌（大牌）和牌型
        mcards=self.outList[self.markPlayerId]
        r=compareCards(data, mcards, self.cardOrder)
        if r!=BIGGER:
            #牌型不对大牌者或不大于大牌者
            return UNDO
        
        #合法可出，出完后标记该玩家
        self.setMarkPlayerId(player)
        return OK

class AI(general.AI):
    '''跟花游戏的AI'''
    def __init__(self):
        super(AI, self).__init__()
    
    def initData(self):
        '''初始化游戏数据'''
        #获取基数牌
        cardBase=self.player.gameData['cardBase']
        bcIndex=CONF['cardBases'].index(cardBase)
        self.baseCard=baseCards[bcIndex]
        #获取牌序
        self.order=self.player.gameData['cardOrder']
    
    def playCard(self):
        '''实现出牌提示逻辑，这里是尽量出可出的牌'''
        markPlayerId=self.player.gameData['markPlayerId']
        if markPlayerId==self.player.info['id']:
            if round==1 and (self.baseCard.id in self.player.cardList):
                #TODO:待改进
                #首轮有最小牌，直接出
                return [self.baseCard.id]
            
            #上次被标记的是自己（大），此次任意出牌
            cards=calcCards(self.player.cardList, [], self.order)
            return cards

        #取得上次被标记玩家出的牌（大牌）
        outList=self.player.gameData['outList']
        mcards=outList[markPlayerId]
        cards=calcCards(self.player.cardList, mcards, self.order)
        return cards


if __name__ == "__main__":
    cardsList=[
        [Card(DIAMOND, ACE), Card(DIAMOND, THREE), Card(DIAMOND, FIVE), Card(DIAMOND, FOUR), Card(DIAMOND, TWO)], 
        [Card(CLUB, ACE), Card(CLUB, THREE), Card(DIAMOND, FIVE), Card(SPADE, FOUR), Card(DIAMOND, TWO)], 
        [Card(CLUB, ACE), Card(DIAMOND, FIVE), Card(CLUB, FIVE), Card(SPADE, ACE), Card(SPADE, FIVE)], 
        [Card(HEART, FIVE), Card(DIAMOND, FIVE), Card(CLUB, FIVE), Card(SPADE, ACE), Card(SPADE, FIVE)], 
        [Card(CLUB, ACE), Card(CLUB, THREE), Card(CLUB, FIVE), Card(CLUB, FOUR), Card(CLUB, SIX)], 
        [Card(CLUB, ACE), Card(CLUB, THREE), Card(HEART, FIVE), Card(CLUB, FOUR), Card(CLUB, SIX)],
        [Card(CLUB, THREE)], 
        [Card(CLUB, THREE), Card(HEART, THREE)], 
        [Card(CLUB, THREE), Card(HEART, THREE), Card(DIAMOND, THREE)], 
    ]
    for ocards in cardsList:
        cards=[c.id for c in ocards]
        type, base=getCardType(cards)
        print type, base
    
    ocards=[
            Card(DIAMOND, ACE), Card(CLUB, ACE), Card(HEART, TWO), 
            Card(DIAMOND, THREE), Card(DIAMOND, FOUR), Card(DIAMOND, FIVE),
            Card(DIAMOND, SIX), Card(DIAMOND, SEVEN), Card(HEART, SEVEN), 
            Card(SPADE, SEVEN), Card(HEART, EIGHT), Card(HEART, NINE), 
            Card(CLUB, JACK), Card(HEART, JACK), Card(SPADE, JACK),
    ]
    cards=[oc.id for oc in ocards]
    
    mocards=[Card(DIAMOND, ACE), Card(SPADE, THREE), Card(DIAMOND, FIVE), Card(DIAMOND, FOUR), Card(DIAMOND, TWO)]
    mocards=[Card(DIAMOND, ACE), Card(DIAMOND, THREE), Card(DIAMOND, FIVE), Card(DIAMOND, FOUR), Card(DIAMOND, TWO)]
    mocards=[Card(DIAMOND, NINE), Card(DIAMOND, THREE), Card(DIAMOND, FIVE), Card(DIAMOND, FOUR), Card(DIAMOND, TWO)]
    mocards=[Card(DIAMOND, NINE), Card(HEART, NINE), Card(SPADE, NINE), Card(SPADE, TWO), Card(DIAMOND, TWO)]
    mcards=[oc.id for oc in mocards]
    
    r=calcCards(cards, mcards, range(54))
    for c in r:
        print Card(c), 
    print 
